﻿using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Test_WebDriver.Utilities
{
	/// <summary>
	///		<para>Summary: Framework for Web Testing Based On Selemiun.</para>
	///		<para>Author: Heller (Hellersong@outlook.com)</para>
	///		<para>Creation: 7/11/2015 10:22:41 AM</para>
	/// </summary>
	public class WebUtility
	{
		//*//Fields
		private static bool debugMode = true;
		private const int defaultBrowserType = BrowserType.IE;
		private const int defaultTimeout = 120;	// 120 Second <=> 2 Minutes
		private static IWebDriver myDriver = null;
		private static WebDriverWait myWait = null;
		private static IJavaScriptExecutor myJsExecutor = null;
		private static Actions myActions = null;
		private static String jsBorder = "arguments[0].style.border='2.5px orange solid';";
		private static String jsNoBorder = "arguments[0].style.border='';";


		//*// Properties
		public static IWebDriver Driver { get { return myDriver; } }
		public static WebDriverWait Wait { get { return myWait; } }
		public static IJavaScriptExecutor JsExecutor { get { return myJsExecutor; } }
		public static Actions Actions { get { return myActions; } }


		//*// Driver Creation for Single Instance
		private WebUtility() { }

		public static void CreateDriver()
		{
			CreateDriver(defaultBrowserType, defaultBrowserType);
		}

		public static void CreateDriver(int browserType)
		{
			CreateDriver(browserType, defaultBrowserType);
		}

		public static void CreateDriver(int browserType, int timeoutSeconds)
		{
			if (browserType == BrowserType.IE)
			{
				ProcessUtility.EndProcess(ProcessUtility.EXE_SELENIUM_IESERVER);
				// Ignore the IE Security Protected Mode
				InternetExplorerOptions options = new InternetExplorerOptions();
				options.IntroduceInstabilityByIgnoringProtectedModeSettings = true;
				myDriver = new InternetExplorerDriver(options);
			}
			if (browserType == BrowserType.FIREFOX)
			{
				ProcessUtility.EndProcess(ProcessUtility.EXE_BROWSER_FIREFOX);
				myDriver = new FirefoxDriver();
			}
			if (browserType == BrowserType.CHROME)
			{
				ProcessUtility.EndProcess(ProcessUtility.EXE_BROWSER_CHROME);
				myDriver = new ChromeDriver();
			}

			myDriver.Manage().Window.Maximize();

			myWait = new WebDriverWait(myDriver, TimeSpan.FromSeconds(timeoutSeconds));
			myJsExecutor = (IJavaScriptExecutor)myDriver;
			myActions = new Actions(myDriver);
		}


		//*// Driver Destroy
		/// <summary>
		///		Handle the a separate window for advertisement situation.
		/// </summary>
		private static void closeWebDriver(IWebDriver driver)
		{
			if (driver == null)
				return;

			try
			{
				if (driver.WindowHandles.Count > 1) // Multi browser windows opened
				{
					String current = driver.CurrentWindowHandle;
					ReadOnlyCollection<String> otherWins = driver.WindowHandles;
					foreach (String winId in otherWins)
					{
						if (winId.Equals(current))
							continue;
						else
							driver.SwitchTo().Window(winId).Close();
					}
				}
				else
				{
					driver.Close();
				}
				
			}
			catch (Exception)
			{
				//ignore  
			}
			finally
			{
				driver.Quit();
			}
		}

		public static void DestroyDriver()
		{
			try
			{
				closeWebDriver(myDriver);
				if (myDriver.GetType() == typeof(InternetExplorerDriver))
				{
					ProcessUtility.EndProcess(ProcessUtility.EXE_SELENIUM_IESERVER);
				}
				if(myDriver.GetType() == typeof(ChromeDriver))
				{
					ProcessUtility.EndProcess(ProcessUtility.EXE_SELENIUM_CHROMESERVER);
				}
			}
			catch (Exception)
			{
			}
			finally
			{
				if(myWait != null)
					myWait = null;
				if(myJsExecutor != null)
					myJsExecutor = null;
				if(myActions != null)
					myActions = null;
				if(myDriver != null)
					myDriver = null;
			}
		}


		//*// Private Methods
		private static IWebElement getElement(By by)
		{
			return Wait.Until(d => { return myDriver.FindElement(by); });
		}

		private static IWebElement getElementInContext(By by, ISearchContext context)
		{
			return Wait.Until(d => { return context.FindElement(by); });
		}

		private static ReadOnlyCollection<IWebElement> getElements(By by)
		{
			return Wait.Until(d => { return myDriver.FindElements(by); });
		}

		private static ReadOnlyCollection<IWebElement> getElementsInContext(By by, ISearchContext context)
		{
			return Wait.Until(d => { return context.FindElements(by); });
		}

		public static void afterFindElement(IWebElement element)
		{
			//JsExcutor.ExecuteScript("arguments[0].style.background='#f4eaf1'", element);
			if (element != null)
			{
				// Some mechanism for trustable click
				Wait.Until(d => { return element.Displayed; });
				//int waitSecond = 5;
				//while(waitSecond > 0 && !element.Displayed)
				//{
				//	try
				//	{
				//		Thread.Sleep(1000);
				//		waitSecond--;
				//	}
				//	catch (Exception)
				//	{
				//	}
				//}

				// We can add some way to show border arround the element
				if (debugMode)
				{
					JsExecutor.ExecuteScript(jsBorder, element);
					PauseSecond(1.5);
					JsExecutor.ExecuteScript(jsNoBorder, element);
				}
			}
		}

		public static void afterFindElement(ReadOnlyCollection<IWebElement> eCollection)
		{
			//JsExcutor.ExecuteScript("arguments[0].style.background='#f4eaf1'", element);

			if (eCollection != null)
			{
				// We can add some way to show border arround the element
				if (debugMode)
				{
					foreach (IWebElement e in eCollection)
					{
						JsExecutor.ExecuteScript(jsBorder, e);
					}
					PauseSecond(1.5);
					foreach (IWebElement e in eCollection)
					{
						JsExecutor.ExecuteScript(jsNoBorder, e);
					}
				}
				PauseSecond();
			}
		}


		//*// Public Methods Basic Using
		public static void PauseSecond()
		{
			try
			{
				Thread.Sleep(2000);
			}
			catch (Exception e)
			{
			}
		}

		public static void PauseSecond(double seconds)
		{
			try
			{
				int time = (int)(seconds * 1000);
				Thread.Sleep(time);
			}
			catch (Exception e)
			{
			}
		}


		//*// Public Methods
		public static void OpenUrl(String url)
		{
			if (myDriver != null)
			{
				// Add pause as sometimes page cannot display when go to
				PauseSecond();
				myDriver.Navigate().GoToUrl(url);
			}
			else
			{
				throw new NullReferenceException("WebUtility.Driver is null.");
			}
		}

		// By.Name
		public static IWebElement GetElementByName(String elementName)
		{
			IWebElement element = null;

			element = getElement(By.Name(elementName));
			afterFindElement(element);

			return element;
		}

		public static IWebElement GetElementByName(String elementName, ISearchContext context)
		{
			IWebElement element = null;

			if (context != null)
			{
				element = getElementInContext(By.Name(elementName), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(element);

			return element;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByName(String elementName)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			eCollection = getElements(By.Name(elementName));
			afterFindElement(eCollection);

			return eCollection;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByName(String elementName, ISearchContext context)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			if (context != null)
			{
				eCollection = getElementsInContext(By.Name(elementName), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(eCollection);

			return eCollection;
		}

		// By.Id (No GetElements Methods)
		public static IWebElement GetElementById(String elementId)
		{
			IWebElement element = null;

			element = getElement(By.Id(elementId));
			afterFindElement(element);

			return element;
		}

		public static IWebElement GetElementById(String elementId, ISearchContext context)
		{
			IWebElement element = null;

			if (context != null)
			{
				element = getElementInContext(By.Id(elementId), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(element);

			return element;
		}

		// By.Tag
		public static IWebElement GetElementByTagName(String elementTagName)
		{
			IWebElement element = null;

			element = getElement(By.TagName(elementTagName));
			afterFindElement(element);

			return element;
		}

		public static IWebElement GetElementByTagName(String elementTagName, ISearchContext context)
		{
			IWebElement element = null;

			if (context != null)
			{
				element = getElementInContext(By.TagName(elementTagName), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(element);

			return element;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByTagName(String elementTagName)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			eCollection = getElements(By.TagName(elementTagName));
			afterFindElement(eCollection);

			return eCollection;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByTagName(String elementTagName, ISearchContext context)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			if (context != null)
			{
				eCollection = getElementsInContext(By.TagName(elementTagName), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(eCollection);

			return eCollection;
		}

		// By.ClassName
		public static IWebElement GetElementByClassName(String className)
		{
			IWebElement element = null;

			element = getElement(By.ClassName(className));
			afterFindElement(element);

			return element;
		}

		public static IWebElement GetElementByClassName(String className, ISearchContext context)
		{
			IWebElement element = null;

			if (context != null)
			{
				element = getElementInContext(By.ClassName(className), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(element);

			return element;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByClassName(String className)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			eCollection = getElements(By.ClassName(className));
			afterFindElement(eCollection);

			return eCollection;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByClassName(String className, ISearchContext context)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			if (context != null)
			{
				eCollection = getElementsInContext(By.ClassName(className), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(eCollection);

			return eCollection;
		}

		// By.XPath
		public static IWebElement GetElementByXPath(String xPath)
		{
			IWebElement element = null;

			element = getElement(By.XPath(xPath));
			afterFindElement(element);

			return element;
		}

		public static IWebElement GetElementByXPath(String xPath, ISearchContext context)
		{
			IWebElement element = null;

			if (context != null)
			{
				element = getElementInContext(By.XPath(xPath), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(element);

			return element;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByXPath(String xPath)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			eCollection = getElements(By.XPath(xPath));
			afterFindElement(eCollection);

			return eCollection;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByXPath(String xPath, ISearchContext context)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			if (context != null)
			{
				eCollection = getElementsInContext(By.XPath(xPath), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(eCollection);

			return eCollection;
		}

		// By.CssSelector
		public static IWebElement GetElementByCssSelector(String cssSelector)
		{
			IWebElement element = null;

			element = getElement(By.CssSelector(cssSelector));
			afterFindElement(element);

			return element;
		}

		public static IWebElement GetElementByCssSelector(String xPath, ISearchContext cssSelector)
		{
			IWebElement element = null;

			if (cssSelector != null)
			{
				element = getElementInContext(By.CssSelector(xPath), cssSelector);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(element);

			return element;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByCssSelector(String cssSelector)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			eCollection = getElements(By.CssSelector(cssSelector));
			afterFindElement(eCollection);

			return eCollection;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByCssSelector(String cssSelector, ISearchContext context)
		{
			ReadOnlyCollection<IWebElement> eCollection = null;

			if (context != null)
			{
				eCollection = getElementsInContext(By.CssSelector(cssSelector), context);
			}
			else
			{
				throw new ArgumentNullException("ISearchContext is null.");
			}
			afterFindElement(eCollection);

			return eCollection;
		}

		// By InnerText
		public static IWebElement GetElementByInnerText(IList<IWebElement> list, String innerText, Boolean isContains = false)
		{
			IWebElement element = null;

			foreach (IWebElement e in list)
			{
				if (isContains)
				{
					if (e.Text.Contains(innerText))
					{
						element = e;
						break;
					}
				}
				else
				{
					if (e.Text == innerText)
					{
						element = e;
						break;
					}
				}
			}

			return element;
		}

		public static ReadOnlyCollection<IWebElement> GetElementsByInnerText(IList<IWebElement> list, String innerText, Boolean isContains = false)
		{
			Collection<IWebElement> eCollection = null;

			foreach (IWebElement e in list)
			{
				if (isContains)
				{
					if (e.Text.Contains(innerText))
					{
						eCollection.Add(e);
					}
				}
				else
				{
					if (e.Text == innerText)
					{
						eCollection.Add(e);
					}
				}
			}

			return new ReadOnlyCollection<IWebElement>(eCollection);
		}


		public static void SetValue(String value, IWebElement element)
		{
			myJsExecutor.ExecuteScript("arguments[0].value='" + value + "';", element);
			PauseSecond();
		}

		public static void ClickElementByJs(IWebElement element)
		{
			if (element != null)
			{
				JsExecutor.ExecuteScript("arguments[0].click();", element);
			}
			else
			{
				throw new NotFoundException("WebElement not found.");
			}
		}

		public static void ClickElementByKeyboard(IWebElement element)
		{
			if (element != null)
			{
				myActions.SendKeys(element, Keys.Return);
				myActions.Build().Perform();
			}
			else
			{
				throw new NotFoundException("WebElement not found.");
			}
		}

		public static void ClickElementInFrame(IWebElement iFrame, IWebElement element)
		{
			if (iFrame != null)
			{
				if (element != null)
				{
					// iframe is not automatically loaded when any action cause page refresh
					String parentHandle = myDriver.CurrentWindowHandle;
					myDriver.SwitchTo().Frame(iFrame);
					element.Click();
					myDriver.SwitchTo().Window(parentHandle);
				}
				else
				{
					throw new NotFoundException("WebElement not found.");
				}
			}
			else
			{
				throw new NotFoundException("Frame not found.");
			}
		}

		public static void RightClickElement(IWebElement element)
		{
			if (element != null)
			{
				myActions.ContextClick(element);
				myActions.Build().Perform();
			}
			else
			{
				throw new NotFoundException("WebElement not found.");
			}
		}

		public static void DoubleClickElement(IWebElement element)
		{
			if (element != null)
			{
				myActions.DoubleClick(element);
				myActions.Build().Perform();
			}
			else
			{
				throw new NotFoundException("WebElement not found.");
			}
		}

		public static void HoverElement(IWebElement element)
		{
			if (element != null)
			{
				myActions.MoveToElement(element);
				myActions.Build().Perform();
			}
			else
			{
				throw new NotFoundException("WebElement not found.");
			}
		}

		// The "Select" web element
		public static void SelectOptionByValue(SelectElement select, String value)
		{
			if (select != null)
			{
				select.SelectByValue(value);
			}
			else
			{
				throw new ArgumentNullException("Select is null.");
			}
		}

		public static void SelectOptionByVisibleText(SelectElement select, String visibleText)
		{
			if (select != null)
			{
				select.SelectByText(visibleText);
			}
			else
			{
				throw new ArgumentNullException("Select is null.");
			}
		}

		public static bool IsOptionExistByVisibleText(SelectElement select, String visibleText)
		{
			bool findFlag = false;

			foreach (IWebElement e in select.Options)
			{
				if (e.Text.Equals(visibleText))
				{
					findFlag = true;
					break;
				}
			}

			return findFlag;
		}

		
		// Window Handles
		public static void SwitchToPopupWindow()
		{
			if (myDriver != null)
			{
				Wait.Until(q => { return myDriver.WindowHandles.Count > 1; });
				int windowCount = myDriver.WindowHandles.Count;
				String popupHandle = myDriver.WindowHandles[windowCount - 1];
				myDriver.SwitchTo().Window(popupHandle);
			}
		}
		public static void SwitchToIFrame(IWebElement frameElement)
		{
			myDriver.SwitchTo().Frame(frameElement);
		}

		public static void SwitchToDefault()
		{
			myDriver.SwitchTo().DefaultContent();
		}

		public static void CloseAlertByAccept()
		{
			IAlert alert = myDriver.SwitchTo().Alert();
			PauseSecond(1);
			alert.Accept();
			PauseSecond(1);
			myDriver.SwitchTo().DefaultContent();
			PauseSecond(1);
		}

		public static void CloseAlertByDismiss()
		{
			IAlert alert = myDriver.SwitchTo().Alert();
			PauseSecond(1);
			alert.Dismiss();
			PauseSecond(1);
			myDriver.SwitchTo().DefaultContent();
			PauseSecond(1);
		}


	}
}
